Skip to content

Phase 1.A–1.D depth, Track 2 audit, decision-gate v3 + Live 12 catalog completion - #18

Merged
slittycode merged 2 commits into
mainfrom
feat/phase-1-depth-audit-and-gate-v3
May 13, 2026
Merged

Phase 1.A–1.D depth, Track 2 audit, decision-gate v3 + Live 12 catalog completion#18
slittycode merged 2 commits into
mainfrom
feat/phase-1-depth-audit-and-gate-v3

Conversation

@slittycode

Copy link
Copy Markdown
Owner

Substantial measurement-depth pass on the ASA Phase 1 pipeline plus a trustworthy multi-model decision-gate harness.

Headline results

  • Default model (gemini-2.5-flash) now PASSes the stem-aware decision gate on the Vtss bench track (was 4× FAIL pre-session).
  • Live 12 catalog completion drops UNKNOWN_PARAMETER 16 → 1 across all v3.1 snapshots via the no-Gemini-spend replay harness.
  • Real-track measurement-quality bugs fixed: bassDetail.averageDecayMs = 0 (Codex PDF finding) and vocalDetail.hasVocals = true false positive on instrumental synth content.

Diff scope

Full per-stage narrative in apps/backend/.runtime/reports/SESSION_BLITZ_2026-05-12.md (750 lines, four continuation appendices) — that file is gitignored (lives under .runtime/), so attached separately to the discussion if reviewers want it. The commit message above also covers the major beats.

Tests

  • Backend: 374 tests pass (was 363; +11 Phase2CatalogValidationTests for alias resolution + Glue Compressor expansion + negative cases).
  • Frontend: 296 tests pass (was 286; +10 pathCoversTracked bidirectional + wildcard tests + tracked-paths inventory guard).
  • UI lint: clean (tsc --noEmit).
  • Replay harness (apps/backend/scripts/replay_catalog_validation.py): UNKNOWN_PARAMETER 16 → 1 across 8 v3.1 snapshots, no Gemini spend.

What's intentionally NOT in this PR

Documented as follow-ups in the SESSION_BLITZ + plan files:

  1. Pro-model citation-depth iteration. gemini-3-pro-preview + gemini-3.1-pro-preview cite far less Phase 1.C/D than 2.5-flash despite the prompt enumeration.
  2. gemini-3.1-pro-preview DROPPED_INVALID_ARRAY_ITEM salvage regression observed in earlier v3 runs.
  3. Phase 1.D feat: value-first backport — genre profiles, MixDoctor, broken-pipe fixes #1 madmom integration — install prototype proven (writeup at .runtime/reports/madmom_install_prototype_2026-05-12.md); not yet wired as conditional analyzer.
  4. Phase 1.D Add phase1 visual story v2 deck assets and build scripts #2 chord progression deeper labelling (librosa chroma_cqt + 24-state Viterbi).
  5. Remaining UNKNOWN_PARAMETER / UNKNOWN_DEVICE prompt targets: Compressor "Sustain", "Ableton Project Settings", "Mixer" — v3.2 prompt-fix work.
  6. JSON_SCHEMA.md documentation of the catalog parameterAliases field (flagged as a doc pass).

Reviewer notes

  • The Vtss bench audio is gitignored (copyrighted, user-owned); the directory README is included so the bench shape is checked in.
  • Frontend lint script only type-checks src/ (per tsconfig.json); test files and vitest configs are excluded by design.
  • The bidirectional + wildcard matcher in phase2Validator.ts is the load-bearing change for the gate's depth metric — tests at tests/services/phase2Validator.test.ts exercise both directions plus the stem-wildcard semantics. Worth a closer read.
  • Backend stage tests in tests/test_server.Phase2CatalogValidationTests exercise the alias mechanism directly without Gemini mocking.

🤖 Generated with Claude Code

…+ catalog completion

Substantial measurement-depth pass on the ASA Phase 1 pipeline plus a
trustworthy multi-model decision-gate harness. Default Gemini model
(gemini-2.5-flash) now PASSes the stem-aware gate on the Vtss bench
track; Live 12 catalog completion drops UNKNOWN_PARAMETER 16 → 1
across all v3.1 snapshots.

## Depth additions (Phase 1.A – 1.D)

- Phase 1.A cheap wins (5): per-frame short-term LUFS curve, per-frame
  7-band spectral balance time series, instantaneous tempoCurve,
  per-second stereo correlationCurve, 256-point arrangement
  noveltyCurve (up from 64).
- Phase 1.B stem-first refactor: per-stem subtree under
  ``stemAnalysis.{drums,bass,other,vocals}`` with the same shape as
  the full-mix analyzers. Phase 2 can now cite element-specific
  measurements.
- Phase 1.C #0 real time-signature detection (onset-accent
  autocorrelation, replaces the always-4/4 fallback).
- Phase 1.C #1 per-band transient density across the 7
  spectralBalance bands (kick / hi-hat density anchors).
- Phase 1.C #2 per-band stereo correlations (Utility-width-per-band
  recommendations).
- Phase 1.C #3 ``grooveDetail.perDrumSwing.{kick,snare,hihat}`` from
  the mid-band beat-loudness signal.
- Phase 1.C #4 snareDetail / hihatDetail band-limited drum character
  (hits, attack sharpness, body/snap energy ratio, decay).
- Phase 1.C #5 saturationDetail (clipped sample count, peakRatio95to50,
  rmsToPeakRatioDb, saturationLikely).
- Phase 1.C #6 32nd-note sidechain resolution + envelopeShape32
  (legacy envelopeShape preserved via max-pairing downsample);
  pumpingRate now four-valued including thirty_second.
- Phase 1.D #5 RT60 reverb per stem with perBandRt60 (low / lowMids /
  highMids / highs) + preDelayMs via Schroeder-equivalent slope fit.
  Wired through stemAnalysis.{stem}.reverbDetail.

## Track 2 audit pass 1 + measurement-quality fixes

- New ``scripts/audit_pass1.py`` harness — structured 12-section
  markdown audit comparing real-track Phase 1 measurements against
  declared ground truth and sanity rules.
- Bug fix: ``bassDetail.averageDecayMs`` was 0 ms on real bass
  material (Codex PDF finding reproduced). Root cause: decay loop
  searched from onset rather than peak, and checked the raw
  oscillating waveform rather than an envelope. Fixed with a 20 ms
  RMS envelope + peak-anchored search. Synthetic validation: 350 ms
  theoretical → 346 ms measured; 35 ms → 37 ms. Real-track Vtss:
  0 ms (FAIL) → 66–125 ms (PASS).
- Bug fix: ``vocalDetail.hasVocals = true`` false positive on
  instrumental synth content. Added temporal-formant-stability check
  (sustained synth leads have near-static "formants"; real vocals
  shift formants 100+ Hz with syllables); tightened formant tolerance
  200 → 100 Hz; raised threshold 0.45 → 0.55. No-stem run on Vtss now
  correctly reports hasVocals=false at conf 0.48.
- Pre-existing import bugs in ``analyze_segments.py``:
  ``_compute_stereo_metrics`` (from dsp_utils) and ``Counter`` (from
  collections) were undefined. ``segmentStereo`` + ``chordDetail``
  now populate correctly.

## Phase 2 contract + validator + decision gate

- Phase 2 prompt v3: per-stem path enumeration, citation contract
  (``phase1Fields`` required on every recommendation), "Phase-2
  self-reference is not a citation" rule, dedicated sections for
  snare/hi-hat character, saturation, sidechain envelope, reverb
  detail.
- Phase 2 schema (``server_phase2.py``): ``phase1Fields`` array
  required on ``mixAndMasterChain``, ``abletonRecommendations``, and
  ``secretSauce.workflowSteps`` items.
- Frontend validator (``phase2Validator.ts``) overhaul:
    * 8 violation types (NUMERIC_OVERRIDE, GENRE_IGNORES_DSP,
      BOUNDS_VIOLATION, MISSING_CITATION, TRIVIAL_CITATIONS,
      NEW_FIELD_UNCITED, LOW_CONFIDENCE_NOT_HEDGED, RECOMMENDATION_SALVAGED).
    * Bidirectional + wildcard ``pathCoversTracked`` matcher so leaf
      citations satisfy parent tracked paths and ``stemAnalysis.*``
      wildcards work.
    * 19 tracked Phase 1.A + Phase 1.C/D field paths (was 7).
- Decision-gate vitests (4): single-, multi-, real-, and stem-aware
  multi-model comparators that read ``/tmp/decision_gate_*.json``
  snapshots and emit per-model markdown reports.

## Live 12 catalog completion (UNKNOWN_PARAMETER closure)

- Auto Filter: ``parameterAliases`` flat-map
  ``{"Filter Resonance": "Resonance", "Filter Frequency": "Frequency"}``.
  Closes the instrument-side long-form naming bleed.
- Glue Compressor: ``allowedParameters`` expanded by 4
  (Range, Sidechain, Sidechain Gain, Sidechain Dry/Wet).
- ``_validate_phase2_catalog_entry`` resolves aliases before the
  membership check; startup-time loader validates the new
  ``parameterAliases`` shape.
- New ``Phase2CatalogValidationTests`` (11 cases) covers alias
  resolution, expansion, scoping per-device, and negative cases.
- New ``scripts/replay_catalog_validation.py`` — no-Gemini-spend
  harness that re-validates v3.1 snapshots against the live catalog.
  Verdict: UNKNOWN_PARAMETER drops 16 → 1 across all 8 snapshots;
  the remaining hit is ``EQ Eight / "Band 8 Filter Type"`` (separate
  device, v3.2-target).

## Test state

- Backend: 374 tests pass (was 363; +11 ``Phase2CatalogValidationTests``).
- Frontend: 296 tests pass (was 286; +10 validator bidirectional /
  wildcard tests).
- UI lint clean (``tsc --noEmit``).

## Out of scope / follow-ups (documented in SESSION_BLITZ + plan)

- Pro-model citation-depth iteration (3-pro and 3.1-pro still cite
  far less Phase 1.C/D than 2.5-flash).
- gemini-3.1-pro-preview ``DROPPED_INVALID_ARRAY_ITEM`` salvage
  regression seen in earlier v3 runs.
- Compressor "Sustain", "Ableton Project Settings", "Mixer" device
  hits — v3.2 prompt-fix targets.
- Phase 1.D #1 madmom integration (prototype proven, install path
  documented at ``.runtime/reports/madmom_install_prototype_2026-05-12.md``).
- Phase 1.D #2 chord progression deeper labelling.
- ``JSON_SCHEMA.md`` documentation of the catalog schema
  (``parameterAliases`` field).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@slittycode slittycode left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: REQUEST CHANGES

Summary

Large but coherent Phase 1 depth + validation gate pass. The type split (types.tstypes/{measurement,interpretation,backend}.ts) is clean, the Phase 1 contract is purely additive, and the phase boundary rules are respected throughout. The decision is forced by missing unit tests on the new Phase 1.C analyzers and dsp_utils.py — the CLAUDE.md bar is explicit here and several non-trivial functions shipped with zero test coverage.

Findings

Blocking

1. Phase 1.C analyzers have no unit test class (analyze_detection.py, analyze.py)

analyze_snare_detail, analyze_hihat_detail, _analyze_band_drum_detail, analyze_per_band_transient_density, and analyze_saturation_detail are new non-trivial functions. Their only coverage is that the field name appears in EXPECTED_TOP_LEVEL_KEYS. That's a schema-presence check, not a behavioral test.

_analyze_band_drum_detail alone has ~60 lines of band-pass isolation, energy-envelope peak detection, decay measurement, and a body/snap ratio computation. analyze_saturation_detail counts clipped samples and computes a 95th/50th percentile ratio. analyze_per_band_transient_density runs a seven-band onset detector. None of these have assertions against synthetic inputs.

Minimum needed: null-input guard, schema fields present, and at least one case asserting non-degenerate output for a signal that should trigger the detector. Same pattern as the existing ReverbDetailTests/BassDetailTests classes.

2. _run_per_stem_analyses has no tests (analyze.py)

This is the Phase 1.B stem overlay orchestrator — the function the PR headline rests on. It's ~85 lines of exception-wrapped dispatch over drums/bass/other/vocals stems. If an analyzer throws on a specific stem, that stem's entry is silently dropped. No test exercises the "one stem fails, others succeed" path, nor the "stems=None → returns None" guard, nor that the returned dict shape matches StemAnalysis.

A test using four synthetic per-stem WAVs (or mocked _load_stem_mono) would catch regressions here.

3. dsp_utils.py has no unit tests (new file, 372 lines)

_pearson_corr, _downsample_lufs_array, _compute_tempo_curve_from_ticks, _compute_stereo_correlation_curve, _downsample_band_energies_curve are shared math utilities. A numerical bug in any of them propagates to every analyzer that imports them. _pearson_corr in particular is called inside the sidechain dip-correlation loop and the stereo band-correlation computation — both Phase 1 outputs used by the decision gate.

4. No regression test for averageDecayMs > 0 (analyze.py)

This is the PR's stated headline bug fix: the old decay loop ran on the raw oscillating bass signal and hit threshold in <1 ms on every note. The envelope-based fix is correct. But there is no test asserting averageDecayMs > 50 (or any positive bound) for a synthetic bass pulse. Without it, the bug can regress silently. BassDetailTests has a stem-preference test added in this PR but doesn't assert the decay value.


Should fix

5. pathCoversTracked inline comment is wrong (apps/ui/src/services/phase2Validator.ts:552-553)

"Wildcards only match a single segment — stemAnalysis.* won't match stemAnalysis.drums.spectralBalance"

The implementation contradicts this. The ts.length <= cs.length branch runs for ts = ["stemAnalysis","*"] (len 2) against cs = ["stemAnalysis","drums","spectralBalance"] (len 3), checks both tracked segments, * matches drums, loop ends — returns true. So stemAnalysis.* does match stemAnalysis.drums.spectralBalance under the current code.

No functional breakage today because no tracked path is actually stemAnalysis.*. But the comment will mislead the next person who adds a short wildcard tracked path and expects single-segment matching.


Worth considering

6. fundamentalHz in analyze_bass_detail uses ZCR estimation on a lowpass-filtered signal

ZCR/2 equals f₀ only for pure sinusoids. A real bass line with a strong second harmonic at 2×f₀ will drive the ZCR upward, biasing the estimate. The clamp max(30.0, min(120.0, zcr/2.0)) limits damage but a 35 Hz sub with strong 70 Hz harmonic energy can read as ~55 Hz. JSON_SCHEMA.md doesn't flag this as approximate. If Phase 2 cites bassDetail.fundamentalHz for a sub-bass filter-frequency recommendation, the error could put an 18 dB/oct cut 20 Hz above the actual fundamental. Worth a one-line schema note ("approximation; harmonic-rich signals bias upward; prefer pitchDetail when stems are available").

Test results

PR description claims 374 backend / 296 frontend pass. Could not execute the suite locally (Essentia/torch not in this environment). Test count and class names in the diff are consistent with the description. Failures: none observed in code review.

Phase boundary check

Clean. _finalize_style_profile_authoritative_measurements in server_phase2.py explicitly overwrites Gemini's styleProfile.authoritativeMeasurements.{bpm,key,timeSignature} with Phase 1 measured values after every response, and emits AUTHORITATIVE_MEASUREMENT_OVERRIDDEN warnings when it had to do so. Phase 2/3 code reads Phase 1 output but never re-derives or mutates it. The new validator checks (citation diversity, low-confidence hedging, salvage warnings) surface Phase 2 misbehavior as violations without touching Phase 1 data.


Generated by Claude Code

…rator, and dsp_utils (#19)

Closes the test-coverage gaps flagged in the PR review.

Backend:
- test_analyze.py: new BandDrumDetailTests, SnareDetailTests, HihatDetailTests,
  TransientDensityDetailTests, SaturationDetailTests, RunPerStemAnalysesTests.
  Each covers null/short/silent input guards, schema-field presence, and at
  least one behavioral assertion against a synthetic signal that should trip
  the detector. RunPerStemAnalysesTests covers stems=None, empty dict, all
  loads failing, mono+stereo success, partial-analyzer failure, and the
  stereo-load-skipped path.
- BassDetailTests: regression test for averageDecayMs > 30 ms on a synthetic
  decaying-pulse signal — guards the envelope-based fix from sliding back to
  the pre-fix sub-millisecond range.
- test_dsp_utils.py (new): direct tests for _pearson_corr,
  _downsample_lufs_array, _downsample_band_energies_curve,
  _compute_tempo_curve_from_ticks, and _compute_stereo_correlation_curve,
  with closed-form expected values for perfect/anti/orthogonal correlation,
  constant-input NaN handling, tempo-change detection, and the silent-sub
  None policy.

Docs / comments:
- JSON_SCHEMA.md: flag bassDetail.fundamentalHz as a ZCR approximation that
  biases upward on harmonic-rich basses; steer Phase 2 to prefer
  pitchDetail when stems are available and avoid narrow filter-Q moves
  within ±15 Hz of the value.
- phase2Validator.ts: rewrite the pathCoversTracked docstring so it
  matches what the code actually does (a wildcard token covers all longer
  citation paths under the same prefix; "stemAnalysis.*" does match
  "stemAnalysis.drums.spectralBalance").

Co-authored-by: Claude <noreply@anthropic.com>
@slittycode
slittycode merged commit 94a26fb into main May 13, 2026
2 checks passed
@slittycode
slittycode deleted the feat/phase-1-depth-audit-and-gate-v3 branch May 13, 2026 03:06
slittycode added a commit that referenced this pull request May 13, 2026
…brosa+Viterbi

Replaces the 5-frame median-filter smoothing of Essentia's per-frame
ChordsDetection labels (PR #18) with a 25-state (12 major + 12 minor +
'N' no-chord) Viterbi decoder over librosa.feature.chroma_cqt. The HMM
self-loop prior produces fewer, more stable segments on hard material
(electronic, modal harmony) and exposes a per-segment confidence we can
hedge against. Public schema preserved — chordTimeline[].label still
matches dominantChords convention; chordChangeCount recomputed from the
new timeline.

Backend (apps/backend):
- analyze_segments.py: new helpers _chord_templates_25,
  _viterbi_chord_timeline, _state_label_short/long,
  _normalize_chord_label_for_compare. Viterbi decoder uses L1 mass-overlap
  for emission (N-safe on dense chroma) and L2 cosine similarity for the
  per-segment confidence metric (intuitive [0, 1] range, 0.5 = noise
  floor, 0.7+ = clean match). analyze_chords now also returns
  chordTimelineSource = 'librosa_viterbi' and chordTimelineAgreement (bool
  | None) computed against Essentia's dominantChords[0] after enharmonic
  normalization (Eb <-> D#, Bb <-> A#, etc.).
- JSON_SCHEMA.md: update chordTimeline row for the new engine and
  labelLong field; add rows for labelLong, chordTimelineSource,
  chordTimelineAgreement.
- prompts/phase2_system.txt CHORD TIMELINE section: note the engine
  change, document labelLong, add chordTimelineAgreement hedging rule.
- tests/test_analyze.py: new ChordTimelineViterbiTests class with 6
  tests — synthetic C-major triad fixture, white-noise no-spurious-
  triads invariant, source/agreement field shape, chordChangeCount
  recompute, enharmonic normalization, and template-matrix structure.

Frontend (apps/ui):
- types/measurement.ts: ChordTimelineEntry gains optional labelLong;
  ChordDetail gains chordTimelineSource and chordTimelineAgreement.
- services/backendPhase1Client.ts: replace the as-cast passthrough on
  chordDetail with a tolerant parseOptionalChordDetail helper modeled on
  parseOptionalRhythmTimeline. Malformed nested timeline entries are
  dropped (missing required keys, NaN confidence, endSec < startSec, non-
  records); confidence clamped to [0, 1]; segments sorted by startSec.
  Back-compat with payloads lacking labelLong.
- services/phase2Validator.ts: add chordDetail.chordTimeline and
  chordDetail.chordChangeCount to PHASE1_NEW_FIELD_PATHS; map
  chordDetail.chordTimeline to chordDetail.chordStrength in
  CONFIDENCE_PAIRS so the existing LOW_CONFIDENCE_NOT_HEDGED rule covers
  timeline citations. No new validators or shape changes.
- services/fieldAnalytics.ts: fix the existing chordDetail.chordProgression
  typo (real field is chordDetail.progression); add chordDetail.chordTimeline
  and chordDetail.chordChangeCount to the analytics tracking list.
- tests/services/backendPhase1Client.test.ts: extend chord fixture with
  the new Viterbi fields; add 4 new tests covering the tolerant parser,
  back-compat with missing labelLong, malformed-segment filtering, and
  chordTimelineAgreement coercion.
- tests/services/phase2Validator.test.ts: extend the PHASE1_NEW_FIELD_PATHS
  guard with chord-timeline entries; add new-field-coverage tests for
  chordDetail.chordTimeline (cited vs uncited); add a low-confidence
  hedging test for a chord-citing recommendation with imperative text.

Verification:
- Backend: 439 tests pass (439 before; 6 new chord tests added, 0
  regressions). On the Vtss-CantCatchMe.mp3 bench fixture: 15 segments,
  labels Eb/C/Cm/Db matching dominantChords ['Cm','Eb','C','F'],
  chordChangeCount 14, confidence range [0.54, 0.74],
  chordTimelineAgreement False (honest Eb-vs-Cm disagreement worth
  hedging on a track that bounces between relative major/minor).
- Frontend: 303 tests pass, lint clean.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
slittycode added a commit that referenced this pull request May 13, 2026
…brosa+Viterbi (#20)

Replaces the 5-frame median-filter smoothing of Essentia's per-frame
ChordsDetection labels (PR #18) with a 25-state (12 major + 12 minor +
'N' no-chord) Viterbi decoder over librosa.feature.chroma_cqt. The HMM
self-loop prior produces fewer, more stable segments on hard material
(electronic, modal harmony) and exposes a per-segment confidence we can
hedge against. Public schema preserved — chordTimeline[].label still
matches dominantChords convention; chordChangeCount recomputed from the
new timeline.

Backend (apps/backend):
- analyze_segments.py: new helpers _chord_templates_25,
  _viterbi_chord_timeline, _state_label_short/long,
  _normalize_chord_label_for_compare. Viterbi decoder uses L1 mass-overlap
  for emission (N-safe on dense chroma) and L2 cosine similarity for the
  per-segment confidence metric (intuitive [0, 1] range, 0.5 = noise
  floor, 0.7+ = clean match). analyze_chords now also returns
  chordTimelineSource = 'librosa_viterbi' and chordTimelineAgreement (bool
  | None) computed against Essentia's dominantChords[0] after enharmonic
  normalization (Eb <-> D#, Bb <-> A#, etc.).
- JSON_SCHEMA.md: update chordTimeline row for the new engine and
  labelLong field; add rows for labelLong, chordTimelineSource,
  chordTimelineAgreement.
- prompts/phase2_system.txt CHORD TIMELINE section: note the engine
  change, document labelLong, add chordTimelineAgreement hedging rule.
- tests/test_analyze.py: new ChordTimelineViterbiTests class with 6
  tests — synthetic C-major triad fixture, white-noise no-spurious-
  triads invariant, source/agreement field shape, chordChangeCount
  recompute, enharmonic normalization, and template-matrix structure.

Frontend (apps/ui):
- types/measurement.ts: ChordTimelineEntry gains optional labelLong;
  ChordDetail gains chordTimelineSource and chordTimelineAgreement.
- services/backendPhase1Client.ts: replace the as-cast passthrough on
  chordDetail with a tolerant parseOptionalChordDetail helper modeled on
  parseOptionalRhythmTimeline. Malformed nested timeline entries are
  dropped (missing required keys, NaN confidence, endSec < startSec, non-
  records); confidence clamped to [0, 1]; segments sorted by startSec.
  Back-compat with payloads lacking labelLong.
- services/phase2Validator.ts: add chordDetail.chordTimeline and
  chordDetail.chordChangeCount to PHASE1_NEW_FIELD_PATHS; map
  chordDetail.chordTimeline to chordDetail.chordStrength in
  CONFIDENCE_PAIRS so the existing LOW_CONFIDENCE_NOT_HEDGED rule covers
  timeline citations. No new validators or shape changes.
- services/fieldAnalytics.ts: fix the existing chordDetail.chordProgression
  typo (real field is chordDetail.progression); add chordDetail.chordTimeline
  and chordDetail.chordChangeCount to the analytics tracking list.
- tests/services/backendPhase1Client.test.ts: extend chord fixture with
  the new Viterbi fields; add 4 new tests covering the tolerant parser,
  back-compat with missing labelLong, malformed-segment filtering, and
  chordTimelineAgreement coercion.
- tests/services/phase2Validator.test.ts: extend the PHASE1_NEW_FIELD_PATHS
  guard with chord-timeline entries; add new-field-coverage tests for
  chordDetail.chordTimeline (cited vs uncited); add a low-confidence
  hedging test for a chord-citing recommendation with imperative text.

Verification:
- Backend: 439 tests pass (439 before; 6 new chord tests added, 0
  regressions). On the Vtss-CantCatchMe.mp3 bench fixture: 15 segments,
  labels Eb/C/Cm/Db matching dominantChords ['Cm','Eb','C','F'],
  chordChangeCount 14, confidence range [0.54, 0.74],
  chordTimelineAgreement False (honest Eb-vs-Cm disagreement worth
  hedging on a track that bounces between relative major/minor).
- Frontend: 303 tests pass, lint clean.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant